package cn.xuqiudong.rpc.cluster.balance;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import cn.xuqiudong.rpc.common.model.Invoker;
import cn.xuqiudong.rpc.common.model.XqdRequest;
import cn.xuqiudong.rpc.common.model.XqdUrl;

import java.util.*;

/**
 * 描述: 基于一致性hash算法的负载均衡
 * 一致性哈希的基本原理就是在一个hash环上（如范围0-2^32-1）计算服务器节点的hash值，
 * 如果一个object要寻找应该路由的服务器节点，则计算其hash值，并在环上顺时针查找离它最近的节点。
 * @author Vic.xu
 * @date 2022-03-07 11:03
 */
public class ConsistentHashLoadBalance implements LoadBalance {

    /**
     *   基于一致性hash算法的负载均衡
     *   1. 把url列表转为SortedMap， key为hash，value为url
     *   2. 计算出客户端请求的hash
     *   3. 得到SortedMap中第一个大于hash的url
     *   4. 如果SortedMap没有打印hash的url，则取第一个
     * @param urls
     * @param invoker
     * @return
     */
    @Override
    public XqdUrl doSelect(List<XqdUrl> urls, Invoker invoker) {
        SortedMap<Integer, XqdUrl> sortedMap = toMap(urls);
        int clientHash = getClientHash(invoker);
        //得到大于该Hash值的所有Map
        SortedMap<Integer, XqdUrl> subMap = sortedMap.tailMap(clientHash);
        if (subMap.isEmpty()) {
            //如果没有比该key的hash值大的，则从第一个node开始
            Integer key = sortedMap.firstKey();
            //返回对应的服务器
            return sortedMap.get(key);
        } else {
            //第一个Key就是顺时针过去离node最近的那个结点
            Integer key = subMap.firstKey();
            //返回对应的服务器
            return subMap.get(key);
        }
    }

    /**
     * 计算调用端的hashcode，  应根据相同的参数计算出来， 即相同的参数应落在一个服务端
     * @param invoker
     * @return
     */
    private static int getClientHash(Invoker invoker) {
        HashCodeBuilder builder = new HashCodeBuilder();
        XqdRequest request = invoker.getXqdRequest();
        HashCodeBuilder append = builder.append(request.getInterfaceName()).append(request.getMethodName());
        if (request.getParams() != null) {
            for (Object param : request.getParams()) {
                append.append(param);
            }
        }
        return append.toHashCode();
    }

    /**
     * 转为map， key为hash值()，key为url
     */
    private static SortedMap<Integer, XqdUrl> toMap(List<XqdUrl> urls) {
        SortedMap<Integer, XqdUrl> sortedMap = new TreeMap<>();
        urls.forEach(url -> {
            //通过HashCodeBuilder计算得到的hashCode， 若为负数，则转为绝对值
            int hash = Math.abs(url.hashCode());
            sortedMap.put(hash, url);
        });

        return sortedMap;
    }

}
